package com.gitee.wsl.net.ssdp.service
/*
import pro.leandev.ssdp.message.Headers
import io.sh.upnp.ssdp.SsdpMessageType
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import pro.leandev.core.Logging
import pro.leandev.server.InternalEvent
import pro.leandev.server.InternalEventType
import pro.leandev.ssdp.message.SsdpMessage
import kotlin.coroutines.CoroutineContext
import kotlin.uuid.ExperimentalUuidApi

class ServiceManager(
    private val inboundChannel: Channel<SsdpMessage>,
    private val notificationChannel: Channel<InternalEvent>,
    override val coroutineContext: CoroutineContext
) : CoroutineScope, Logging {
    private val services = mutableListOf<Service>()

    init {
        launch {
            // Redirect notify messages [m] to different processors
            inboundChannel.consumeAsFlow()
                .filter(SsdpMessage::isFromService)
                .collect { msg ->
                    when (msg.type) {
                        SsdpMessageType.Search -> {}
                        SsdpMessageType.NotifyAlive,
                        SsdpMessageType.NotifyUpdate -> processAlive(msg)
                        SsdpMessageType.NotifyByeBye -> processByeBye(msg)
                    }
                }
        }
    }

    /**
     * When a device is added to the network, it shall multicast discovery messages to advertise its
     * root device, any embedded devices, and any services.
     * Message format:
     *      NOTIFY * HTTP/1.1
     *      HOST: 239.255.255.250:1900
     *      CACHE-CONTROL: max-age = seconds until advertisement expires
     *      LOCATION: URL for UPnP description for root device
     *      NT: notification type
     *      NTS: ssdp:alive
     *      SERVER: OS/version UPnP/2.0 product/version
     *      USN: composite identifier for the advertisement
     *      BOOTID.UPNP.ORG: number increased each time device sends an initial announce or an update message
     *      CONFIGID.UPNP.ORG: number used for caching description information
     *      SEARCHPORT.UPNP.ORG: number identifies port on which device responds to unicast M-SEARCH
     *
     * @see "https://openconnectivity.org/upnp-specs/UPnP-arch-DeviceArchitecture-v2.0-20200417.pdf" ch.1.2.2
     *
     * When a new UPnP-enabled interface is added to a multi-homed device, the device shall
     * increase its BOOTID.UPNP.ORG field value, multicast an ssdp:update message for each of
     * the root devices, embedded devices and embedded services to all of the existing UPnP
     * enabled interfaces to announce a change in the BOOTID.UPNP.ORG field value, and readvertise
     * itself on all (existing and new) UPnP-enabled interfaces with the new
     * BOOTID.UPNP.ORG field value. Similarly, if a multi-homed device loses connectivity on a
     * UPnP-enabled interface and regains connectivity, or if the IP address on one of the UPnP
     * enabled interfaces changes, the device shall increase the BOOTID.UPNP.ORG field value,
     * multicast an ssdp:update message for each of the root devices, embedded devices and
     * embedded services to all the unaffected UPnP-enabled interfaces to announce a change in
     * the BOOTID.UPNP.ORG field value, and re-advertise itself on all (affected and unaffected)
     * UPnP-enabled interfaces with the new BOOTID.UPNP.ORG field value. In all cases, the
     * ssdp:update message for the root devices shall be sent as soon as possible. Other
     * ssdp:update messages should be spread over time. However, all ssdp:update messages
     * shall be sent before any announcement messages with the new BOOTID.UPNP.ORG field
     * value can be sent
     * When ssdp:update messages are sent on multiple UPnP-enabled interfaces, the messages
     * shall contain identical field values except for the HOST and LOCATION field values. The
     * HOST field value of an advertisement shall be the standard multicast address specified for the
     * protocol (IPv4 or IPv6) used on the interface. The URL specified in the LOCATION field value
     * shall be reachable on the interface on which the advertisement is sent.
     * Message format:
     *      NOTIFY * HTTP/1.1
     *      HOST: 239.255.255.250:1900
     *      LOCATION: URL for UPnP description for root device
     *      NT: notification type
     *      NTS: ssdp:update
     *      USN: composite identifier for the advertisement
     *      BOOTID.UPNP.ORG: BOOTID value that the device has used in its previous announcements
     *      CONFIGID.UPNP.ORG: number used for caching description information
     *      NEXTBOOTID.UPNP.ORG: new BOOTID value that the device will use in subsequent announcements
     *      SEARCHPORT.UPNP.ORG: number identifies port on which device responds to unicast M-SEARCH
     *
     * @see "https://openconnectivity.org/upnp-specs/UPnP-arch-DeviceArchitecture-v2.0-20200417.pdf" ch.1.2.4
     */
    private suspend fun processAlive(m: SsdpMessage) {
        check(m.getHeader(Headers.Host) == m.address.toString()) { "Cannot add device: wrong Host ${m.address.toString()}" }
        if (m.isExpired()) return
        addDevice(Service.fromSsdpMessage(m))
    }

    /**
     * When a device and its services are going to be removed from the network, the device should
     * multicast an ssdp:byebye message corresponding to each of the ssdp:alive messages it
     * multicasted that have not already expired.
     * Message format:
     *      NOTIFY * HTTP/1.1
     *      HOST: 239.255.255.250:1900
     *      NT: notification type
     *      NTS: ssdp:byebye
     *      USN: composite identifier for the advertisement
     *      BOOTID.UPNP.ORG: number increased each time device sends an initial announce or an update message
     *      CONFIGID.UPNP.ORG: number used for caching description information
     *
     * @see "https://openconnectivity.org/upnp-specs/UPnP-arch-DeviceArchitecture-v2.0-20200417.pdf" ch.1.2.3
     */
    private suspend fun processByeBye(m: SsdpMessage) {
        check(m.getHeader(Headers.Host) == m.address.toString()) { "Cannot remove device: wrong Host ${m.address.toString()}" }
        removeDevice(m.getRequiredHeader(Headers.UniqueServiceName))
    }

    @OptIn(ExperimentalUuidApi::class)
    private suspend fun addDevice(s: Service) {
        // check if device was already discovered
        services.firstOrNull { s.uuid == it.uuid && s.uniqueResourceName == it.uniqueResourceName }
            ?.let { existing ->
                // if re-discovered device has old bootId and don't have nextBootId (not update), leave it intact
                // since it's the same device that was not reconnected
                if (existing.bootId == d.bootId && d.nextBootId == null) return
                // else remove outdated device information
                devices.remove(existing)
                // if it's device update
                d.nextBootId?.let { nbi ->
                    // just update bootId, submit it back and return
                    devices.add(existing.copy(bootId = nbi, nextBootId = null))
                    return
                }
            }
        // add new or re-connected device and notify the ControlPoint
        devices.add(d)
        notificationChannel.send(InternalEvent(InternalEventType.DeviceDiscovered, d))
    }

    private suspend fun removeDevice(usn: String) = devices
        .firstOrNull { d -> d.getUsn() == usn }
        ?.let { d ->
            devices.remove(d)
            notificationChannel.send(InternalEvent(InternalEventType.DeviceRemoved, d))
        }
}

 */